/**
 * 
 */
package com.cma.common.redis;

import com.cma.common.utils.PropertiesUtil;
import org.apache.log4j.Logger;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author xufx
 * 
 */
public class JedisUtil {
	/** -1 不做限制 */
	private static final int REDIS_MAXACTIVE = Integer.parseInt(PropertiesUtil.getProperitiesByKey("REDIS_MAXACTIVE", "-1"));
	/** 最大空闲时间(s) */
	private static final int REDIS_MAXIDLE = Integer.parseInt(PropertiesUtil.getProperitiesByKey("REDIS_MAXIDLE", "3"));
	/** 最大等待时间(ms) */
	private static final long REDIS_MAXWAIT = Long.parseLong(PropertiesUtil.getProperitiesByKey("REDIS_MAXWAIT", "3000"));
	/** sentinel集群信息 */
	private static final String REDIS_SENTINELS = PropertiesUtil.getProperitiesByKey("REDIS_SENTINELS");
	/** mater name */
	private static final String REDIS_MASTERNAME = PropertiesUtil.getProperitiesByKey("REDIS_MASTERNAME");
	/** 连接池 */
	private static final JedisSentinelPool seninelPool = createSentinelPool();

	/** 存储线程内的Jedis实例 */
	private static ThreadLocal<Jedis> repos = new ThreadLocal<>();
	private static ThreadLocal<Transaction> trans = new ThreadLocal<>();
	/** 日志输出 */
	private static final Logger log = Logger.getLogger(JedisUtil.class);

	/** redis集群信息 */
	private static final String REDIS_CLUSTER = PropertiesUtil.getProperitiesByKey("REDIS_CLUSTER");
	/** redis集群 */
	private static final List<JedisPool> jedisPool = createJedisPools();

	/** 缓存实例 */
	private static ThreadLocal<Jedis> readable = new ThreadLocal<>();

	/**
	 * 创建连接池
	 * 
	 * @return
	 */
	private static JedisSentinelPool createSentinelPool() {
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(REDIS_MAXACTIVE);
		config.setMaxIdle(REDIS_MAXIDLE);
		config.setMaxWaitMillis(REDIS_MAXWAIT);
		config.setTestOnBorrow(true);
		config.setTestOnReturn(true);

		// sentinel集群信息
		Set<String> sentinels = new HashSet<String>();
		String[] hostAndPortArr = REDIS_SENTINELS.split(",");
		for (String str : hostAndPortArr) {
			sentinels.add(str);
		}
		// 用sentinel方式创建连接池
		return new JedisSentinelPool(REDIS_MASTERNAME, sentinels, config);
	}

	/**
	 * 创建Redis集群
	 */
	private static List<JedisPool> createJedisPools() {
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(REDIS_MAXACTIVE);
		config.setMaxIdle(REDIS_MAXIDLE);
		config.setMaxWaitMillis(REDIS_MAXWAIT);
		config.setTestOnBorrow(true);
		config.setTestOnReturn(true);

		List<JedisPool> jedisClusterNodes = new ArrayList<JedisPool>();
		String[] hostAndPortArr = REDIS_CLUSTER.split(",");
		String[] hostAndPort;
		for (String str : hostAndPortArr) {
			hostAndPort = str.split(":");
			jedisClusterNodes.add(new JedisPool(config, hostAndPort[0], Integer.parseInt(hostAndPort[1])));
		}
		return jedisClusterNodes;
	}

	/**
	 * 获取连接实例
	 * 
	 * @param useTrans 是否使用事务
	 * @return
	 */
	public static Jedis getJedis() {
		return getJedis(false);
	}

	/**
	 * 获取只读连接实例
	 * 
	 * @return
	 */
	public static Jedis getReadJedis() {
		Jedis readJedis = null;
		JedisPool pool;
		int rand;
		boolean[] isDown = new boolean[jedisPool.size()];
		// 循环查找连接池
		for (int i = 0; i < jedisPool.size();) {
			// 随机一个池
			rand = (int) (Math.random() * jedisPool.size());
			// 如果该池已经被标记为宕机
			if (isDown[rand]) {
				continue;
			}
			i++;

			// 取得当前池
			pool = jedisPool.get(rand);
			try {
				readJedis = pool.getResource();
				log.debug("缓存读取，主机编号：" + rand);
			} catch (JedisConnectionException ex) {
				log.warn(String.format("缓存服务器【%d】已经宕机。", rand));
				isDown[rand] = true;
				continue;
			}
			log.debug("当前可用连接数：" + pool.getNumActive());
			break;
		}

		// 异常处理
		if (readJedis == null) {
			throw new JedisException("无法获取缓存连接。");
		}
		return readJedis;
	}

	/**
	 * 获取连接实例
	 * 
	 * @param useTrans 是否使用事务
	 * @return
	 */
	public static Jedis getJedis(boolean useTrans) {
		// 未获取连接，或连接已经失效时，尝试获取连接（2次）
		Jedis jedis = null;
		for (int i = 1;; i++) {
			try {
				jedis = seninelPool.getResource();
				break;
			} catch (Exception e) {
				log.warn(String.format("第%d次取得连接失败", i), e);
			}
			// 尝试次数
			if (i == 2) {
				break;
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// 忽略错误
			}
		}

		// 异常处理
		if (jedis == null) {
			throw new JedisException("无法获取缓存连接。");
		}

		// 取得连接后开启事务
		if (useTrans) {
			// TODO 还要捕获异常归还链接等
			trans.set(jedis.multi());
		}
		return jedis;
	}

	/**
	 * 释放连接实例
	 * 
	 * @param hasErrors 是否有错误
	 * @return
	 */
	public static void close(boolean hasErrors) {
		// 取得保存的连接
		Jedis jedis = repos.get();
		if (jedis == null) {
			return;
		}
		repos.remove();

		try {
			// 取得保存的事务
			Transaction t = trans.get();
			// 事务存在时
			if (t != null) {
				trans.remove();
				// 有错误时
				if (hasErrors) {
					// 放弃
					t.discard();
				} else {
					// 提交
					t.exec();
				}
			}
		} catch (Exception e) {
			log.warn("事务处理失败。", e);
		}

		try {
			if (log.isDebugEnabled()) {
				log.debug("返还资源");
			}
			jedis.close();
		} catch (Exception e) {
			log.warn("关闭连接处理失败。", e);
		}
	}

	/**
	 * 释放只读连接实例
	 * 
	 */
	public static void closeRead() {
		Jedis readJedis = readable.get();
		if (readJedis == null) {
			return;
		}
		readable.remove();
		try {
			if (log.isDebugEnabled()) {
				log.debug("返还读连接资源");
			}
			readJedis.close();
		} catch (Exception e) {
			log.warn("关闭连接处理失败。", e);
		}
	}

	/**
	 * 关闭连接
	 * 
	 * @param jedis
	 */
	public static void close(Jedis jedis) {
		if (jedis == null) {
			return;
		}

		try {
			// 取得保存的事务
			Transaction t = trans.get();
			// 事务存在时
			if (t != null) {
				trans.remove();
				// 提交
				t.exec();
			}
		} catch (Exception e) {
			log.warn("事务处理失败。", e);
		}

		try {
			if (log.isDebugEnabled()) {
				log.debug("关闭连接");
			}
			jedis.close();
		} catch (Exception e) {
			log.warn("关闭连接处理失败。", e);
		}
	}

	/**
	 * 关闭连接
	 * 
	 * @param jedis
	 */
	public static void closeOnError(Jedis jedis) {
		if (jedis == null) {
			return;
		}

		try {
			// 取得保存的事务
			Transaction t = trans.get();
			// 事务存在时
			if (t != null) {
				trans.remove();
				// 放弃
				t.discard();
			}
		} catch (Exception e) {
			log.warn("事务处理失败。", e);
		}

		try {
			if (log.isDebugEnabled()) {
				log.debug("关闭连接");
			}
			jedis.close();
		} catch (Exception e) {
			log.warn("关闭连接处理失败。", e);
		}
	}

	/**
	 * 释放所有资源
	 */
	public static void release() {
		seninelPool.close();
		seninelPool.destroy();
		for (JedisPool pool : jedisPool) {
			pool.close();
			pool.destroy();
		}
	}
}
